package nachos.threads;

import nachos.machine.*;

import java.util.LinkedList;

/**
 * Uses the hardware timer to provide preemption, and to allow threads to sleep
 * until a certain time.
 */
public class Alarm
{
    private static Alarm alarm;
    public static Alarm alarm()
    {
        if(alarm == null)
            alarm = new Alarm();
        return alarm;
    }

    /**
     * Allocate a new Alarm. Set the machine's timer interrupt handler to this
     * alarm's callback.
     *
     * <p><b>Note</b>: Nachos will not function correctly with more than one
     * alarm.
     */
    public Alarm()
    {
        waitForAlarmThreadList = new LinkedList<>();
        Machine.timer().setInterruptHandler(new Runnable()
        {
            public void run()
            {
                timerInterrupt();
            }
        });
    }

    /**
     * The timer interrupt handler. This is called by the machine's timer
     * periodically (approximately every 500 clock ticks). Causes the current
     * thread to yield, forcing a context switch if there is another thread
     * that should be run.
     */
    public void timerInterrupt()
    {
        boolean preState = Machine.interrupt().disable();//关中断
        for(WaitForAlarmThread t : waitForAlarmThreadList){
            if(t.wakeTime<=Machine.timer().getTime()){//如果达到唤醒时间，将其从链表中移除并唤醒该线程
                waitForAlarmThreadList.remove(t);
                t.thread.ready();
            }
        }
        KThread.currentThread().yield();
        Machine.interrupt().restore(preState);//恢复中断
    }

    /**
     * Put the current thread to sleep for at least <i>x</i> ticks,
     * waking it up in the timer interrupt handler. The thread must be
     * woken up (placed in the scheduler ready set) during the first timer
     * interrupt where
     *
     * <p><blockquote>
     * (current time) >= (WaitUntil called time)+(x)
     * </blockquote>
     *
     * @param    x    the minimum number of clock ticks to wait.
     * @see    Timer#getTime()
     */
    public void waitUntil(long x)
    {
        // for now, cheat just to get something working (busy waiting is bad)
        boolean preState = Machine.interrupt().disable();//关中断
        long wakeTime = Machine.timer().getTime()+x;//计算唤醒的时间
        WaitForAlarmThread waitForAlarmThread = new WaitForAlarmThread(wakeTime, KThread.currentThread());
        waitForAlarmThreadList.add(waitForAlarmThread);//将线程加入到等待链表中
        KThread.sleep();//让该线程睡眠
        Machine.interrupt().restore(preState);//恢复中断
    }

    private LinkedList<WaitForAlarmThread> waitForAlarmThreadList;
}

class WaitForAlarmThread{
    long wakeTime;
    KThread thread;
    public WaitForAlarmThread(long wakeTime,KThread thread){
        this.wakeTime=wakeTime;
        this.thread=thread;
    }
}